kitt_score 0.1.0

Decision engine at the core of Project KITT — in-memory stateful matching with pluggable scoring backends.
Documentation
# Engine and Decision

This note covers `src/decide/*` and `src/engine/*`.

## `Decide`

**Defined in:** `src/decide/mod.rs`

The policy trait that chooses a winning candidate index.

### Signature
`fn decide(&self, candidates: &[Candidate]) -> Option<usize>`

### Role
Separates score production from winner selection.

## `DefaultDecider`

**Defined in:** `src/decide/default.rs`

The built-in decider.

### Behavior
- returns `None` on an empty candidate slice,
- otherwise returns the index of the greatest `score.score`,
- ignores `priority`,
- breaks ties by first position.

### Implication
If you want lexicographic `(priority, score)` ordering, you must inject a custom [[#Decide]] implementation.

## `EngineConfig`

**Defined in:** `src/engine/config.rs`

The resolved internal configuration bag.

### Fields
- `clock: Arc<dyn Clock>`
- `decider: Arc<dyn Decide>`
- `default_vector_backend: VectorBackend`
- `n_shards: usize`
- `embedding_slot: Option<(KindId, AttrId)>`

### Important current-state note
`default_vector_backend` and `n_shards` are present in config, but current runtime code does not meaningfully vary behavior based on them.

## `CompiledScorer`

**Defined in:** `src/engine/compiled_scorer.rs`

The engine's internal executable scorer sum-type.

### Variants
- `Predicate(Program)`
- `VectorLinear { target: Box<[f32]>, metric: VectorMetric }`

### Why it exists
This replaces trait-object dispatch on the hot path with one enum match.

## `BuildError`

**Defined in:** `src/engine/builder.rs`

The error enum returned by [[#EngineBuilder]] when engine construction fails.

### Variants
- `MissingSchema`
- `UnknownKind(String)`
- `UnknownAttr(String)`
- `AttrNotInKind { kind, attr }`
- `EmbeddingSlotNotF32Arr { kind, attr }`

### Role
This protects the engine from invalid startup-time configuration, especially embedding-slot configuration.

## `EngineBuilder<T>`

**Defined in:** `src/engine/builder.rs`

The fluent constructor for [[#Engine]].

### Important methods
- `new`
- `schema`
- `clock`
- `clock_arc`
- `decider`
- `default_vector_backend`
- `n_shards`
- `with_embedding_slot`
- `build`

### Required input
A schema is mandatory.

### Optional inputs
- custom clock,
- custom decider,
- embedding slot,
- default vector backend,
- shard count.

### Important current-state note
The builder exposes more configurability than the current runtime uses. The public API is slightly ahead of the implementation.

## `Ingested<T>`

**Defined in:** `src/engine/ingest.rs`

The result enum returned by all ingest methods.

### Variants
- `Updated`
- `Registered(ActionId)`
- `Decided(Outcome<T>)`
- `NoWinner`
- `ReloadInProgress`
- `Rejected(IngestErr)`

### Role
This is not an exception path only; it is the actual protocol of the engine.

## `Outcome<T>`

**Defined in:** `src/engine/ingest.rs`

The successful trigger outcome.

### Fields
- `action_id`
- `score: ScoreResult`
- `payload: T`

### Semantics
`payload` is already post-processed if the winning action had a post closure.

## `Engine<T>`

**Defined in:** `src/engine/engine.rs`

The main orchestrator of the crate.

### Fields
- `schema: Arc<Schema>`
- `table: Arc<LocationTable<T>>`
- `config: EngineConfig`
- `metrics: Arc<EngineMetrics>`

### Public methods
- `schema`
- `location_count`
- `reload_all`
- `upsert_location`
- `remove_location`
- `ingest_update`
- `ingest_action`
- `ingest_trigger`
- `metrics`
- `builder`

### `ingest_update` semantics
- reject if reload is in progress,
- reject if location does not exist,
- delegate to `LocationState::apply_update`,
- increment update metrics on success.

### `ingest_action` semantics
- reject if reload is in progress,
- reject if location does not exist,
- compile the scorer,
- create an [[Modules/Location State#ActionEntry]],
- insert it into the action list sorted by `end` time,
- increment registration metrics.

### `ingest_trigger` semantics
- reject if reload is in progress,
- reject if location does not exist,
- expire stale actions,
- create a [[Modules/Location State#LocationView]],
- score each live action,
- build [[Modules/Scoring Core#Candidate]] values,
- call the decider,
- clone or post-process the winning payload,
- record trigger metrics.

### Important trigger-path observations
- current eligibility is time-based and location-based, not kind-filtered,
- vector scoring requires an embedding slot configured at build time,
- missing embedding data yields `NEG_INFINITY` during vector scoring,
- metrics are recorded even when the outcome is `NoWinner`.

## Helper behavior worth knowing

### `find_by_action_id`
The engine finds the winning stored action by scanning the action list for the chosen candidate's `ActionId`. This relies on the invariant that candidates are built from the same action list.

### `__test_set_reload`
A hidden test helper that toggles the reload flag directly for integration tests.

## Summary

The engine module is where the crate's abstract pieces become one concrete state machine. It is also where the difference between **declared API semantics** and **currently enforced semantics** is most visible.